/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file upload.c
 * Multipart/form-data and File Upload management.
 */

#include <apr_strings.h>

#include "upload.h"
#include "session.h"
#include "logging.h"
#include "pyhp_layer.h"

#define MULTIPART_BOUNDARY_LEN 9
#define MULTIPART_NAME_LEN 5

#define STATE_END 0
#define STATE_BEGIN 1
#define STATE_FOUND_BOUNDARY 2
#define STATE_CONTENT 3

/**
 * uploaded file clean up, will remove all the uploaded files
 */
void pyhp_upload_clean(PyHPData *pydata, request_rec *r)
{
    PyObject *field_name, *file_dict;
    Py_ssize_t pos = 0;

    PyObject *uploads = pyhp_layer_get_object(pydata, "uploads");

    if (!uploads)
        return;

    if (PyDict_Size(uploads)) {
        while(PyDict_Next(uploads, &pos, &field_name, &file_dict)) {
            PyObject *file = PyDict_GetItemString(file_dict, "file");
            PyObject *file_name = PyObject_GetAttrString(file, "name");
            unlink(PyString_AsString(file_name));
        }
    }
}

/**
 * Looks up text parts inside a mixed text/binary content.
 * It will also update the length of the content reducing it by the size
 * of the part preceding the looked up text.
 *
 * @param what The text to find inside the data
 * @param what_len The length of the text to find inside the data
 * @param request The content where to find the looked up text
 * @param req_len The length of the content
 *
 * @return A pointer to the begin of the found text
 */
static char *multipart_find(char *what, unsigned what_len, char *request,
                             unsigned *req_len)
{
    int i, j;

    for (i = 0; i < *req_len; ++i) {
        for (j = 0; j < what_len; ++j) {
            if (what[j] != request[i+j])
                break;
        }

        if (j == what_len) {
            *req_len -= i;
            return &request[i];
        }
    }

    return NULL;
}


/**
 * Creates and populates a single file upload dictionary entry
 *
 * @param r The request for which has been uploaded the file
 * @param field_name The form field name for which has been uploaded the file
 * @param filename The name of the file uploaded
 * @param mime The mimetype of the uploaded file
 * @param content The file content
 * @param content_len The file size
 *
 * @return a Python dictionary cotaining the file and its metadata
 */
PyObject *pyhp_file_upload(request_rec *r, char *field_name,
                           char *filename, char *mime,
                           char *content, unsigned content_len)
{
    PyObject *obj, *dict;
    pyhp_config *cfg = pyhp_get_config(r);
    char *fname =
        apr_pstrcat(r->pool, cfg->tmp_path,
                    pyhp_session_gen(r),
                    field_name,
                    NULL);

    /*pyhp_logf(r, PyHP_DEBUG,
              "FIELD: %s, CONTENT_SIZE: %u FILENAME: %s MIME: %s",
              field_name, content_len, filename, mime);*/

    FILE *f = fopen(fname, "w");
    fwrite(content, content_len, 1, f);
    obj = PyFile_FromFile(f, fname, "w", fclose);

    dict = PyDict_New();
    PyDict_SetItemString(dict, "file", obj);
    Py_XDECREF(obj);

    if (filename) {
        obj = PyString_FromString(filename);
        PyDict_SetItemString(dict, "filename", obj);
        Py_XDECREF(obj);
    }

    if (mime) {
        obj = PyString_FromString(mime);
        PyDict_SetItemString(dict, "mime", obj);
        Py_XDECREF(obj);
    }

    return dict;
}

/**
 * Parses a multipart form request retrieving the uploaded file and form fields
 *
 * @param r The Apache Request for which to parse the upload
 * @param request The multipart request itself
 * @param req_len The request code size
 * @param get_dict The dictionary where to set the data of the form fields
 * @param file_dict The dictionary where to set the data of the file fields
 */
void pyhp_multipart_form_parse(request_rec *r, char *request, unsigned req_len,
                               PyObject *get_dict, PyObject *file_dict)
{
    int separator_len;
    const char *post_type = apr_table_get(r->headers_in, "Content-Type");
    char *separator = strstr(post_type, "boundary=");
    int state = STATE_BEGIN;
    int ended_parsing = 0;
    char *field_name = NULL;
    char *filename = NULL;
    char *mime = NULL;

    PyObject *dict;

    if (separator)
        separator += MULTIPART_BOUNDARY_LEN;

    separator_len = strlen(separator);

    while (!ended_parsing) {
        char *field_content = NULL;
        int is_file = 0;

        switch (state) {
            case STATE_BEGIN:
                field_name = NULL;
                request = multipart_find(separator, separator_len,
                                         request, &req_len);

                if (!request)
                    state = STATE_END;
                else {
                    request = multipart_find("\n", 1, request, &req_len);
                    if ( (!request) || (req_len == 1) )
                        state = STATE_END;
                    else
                        state = STATE_FOUND_BOUNDARY;
                }
                break;

            case STATE_FOUND_BOUNDARY:
                field_name = multipart_find("name=", MULTIPART_NAME_LEN,
                                            request, &req_len);
                if (!field_name)
                    state = STATE_END;
                else {
                    char *field_end;
                    filename = strstr(field_name, "filename");

                    field_name += MULTIPART_NAME_LEN;
                    field_name += 1; //skip "
                    req_len -= (MULTIPART_NAME_LEN + 1);

                    field_end = multipart_find("\"", 1, field_name, &req_len);
                    request = field_end + 1;
                    --req_len;
                    *field_end = '\0';

                    request = multipart_find("\r\n\r\n", 4, request, &req_len);
                    if (!request)
                        state = STATE_END;
                    else {
                        request += 4;
                        req_len -= 4;
                        state = STATE_CONTENT;
                        if (filename && filename+10 < request) {
                            char *end;
                            mime = strstr(filename, "Content-Type");
                            if (mime && mime+14 < request) {
                                mime += 14;
                                end = strstr(mime, "\r\n");
                                if (end && end < request)
                                    *end = '\0';
                                else
                                    mime = NULL;
                            }

                            filename += 10;
                            end = strstr(filename, "\"");
                            if (end && end < request)
                                *end = '\0';
                            else
                                filename = NULL;
                            dict = file_dict;
                        } else
                            dict = get_dict;
                    }
                }
                break;

            case STATE_CONTENT:
                field_content = request;
                request = multipart_find(separator, separator_len,
                                         request, &req_len);

                if (!request)
                    state = STATE_END;
                else {
                    PyObject *obj;
                    int content_len = request - field_content - 4;

                    if (content_len) {
                        if (dict == file_dict) {
                            obj = pyhp_file_upload(r, field_name,
                                                   filename, mime,
                                                   field_content, content_len);
                        } else
                            obj = PyString_FromStringAndSize(field_content,
                                                             content_len);
                        PyDict_SetItemString(dict, field_name, obj);
                        Py_XDECREF(obj);
                    }
                    state = STATE_BEGIN;
                }
                break;

            case STATE_END:
                if (req_len != 1)
                    pyhp_logf(r, PyHP_ERR,
                              "END WITH ERROR, MISSING %u", req_len);
                ended_parsing = 1;
                break;
        }
    }
}
